/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 * Copyright (c) 2007-2010 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "ivl_alloc.h"
#include "vvp_priv.h"

static ivl_signal_t find_path_source_port(ivl_delaypath_t path) {
  unsigned idx;
  ivl_nexus_t nex = ivl_path_source(path);
  ivl_scope_t path_scope = ivl_path_scope(path);

  for (idx = 0; idx < ivl_nexus_ptrs(nex); idx += 1) {
    ivl_nexus_ptr_t ptr = ivl_nexus_ptr(nex, idx);
    ivl_signal_t sig = ivl_nexus_ptr_sig(ptr);
    if (sig == 0) continue;
    if (ivl_signal_port(sig) == IVL_SIP_NONE) continue;

    /* The path source scope must match the modpath scope.*/
    if (ivl_signal_scope(sig) != path_scope) continue;

    return sig;
  }

  return 0;
}

/*
 * Draw a .modpath record. The label is the label to use for this
 * record. The driver is the label of the net that feeds into the
 * modpath device. (Note that there is only 1 driver.) The path_sig is
 * the signal that is the output of this modpath. From that signal we
 * can find all the modpath source nodes to generate the complete
 * modpath record.
 */
static void draw_modpath_record(const char* label, const char* driver,
                                ivl_signal_t path_sig) {
  unsigned idx;
  typedef const char* ccharp;
  ccharp* src_drivers;
  ccharp* con_drivers;

  unsigned width = ivl_signal_width(path_sig);

  src_drivers = calloc(ivl_signal_npath(path_sig), sizeof(ccharp));
  con_drivers = calloc(ivl_signal_npath(path_sig), sizeof(ccharp));
  for (idx = 0; idx < ivl_signal_npath(path_sig); idx += 1) {
    ivl_delaypath_t path = ivl_signal_path(path_sig, idx);
    ivl_nexus_t src = ivl_path_source(path);
    ivl_nexus_t con = ivl_path_condit(path);

    src_drivers[idx] = draw_net_input(src);

    if (con)
      con_drivers[idx] = draw_net_input(con);
    else if (ivl_path_is_condit(path))
      con_drivers[idx] = "";
    else
      con_drivers[idx] = 0;
  }

  fprintf(vvp_out, "  .scope S_%p;\n",
          ivl_path_scope(ivl_signal_path(path_sig, 0)));
  fprintf(vvp_out, "%s .modpath %u %s v%p_0", label, width, driver, path_sig);

  for (idx = 0; idx < ivl_signal_npath(path_sig); idx += 1) {
    ivl_delaypath_t path = ivl_signal_path(path_sig, idx);
    int ppos = ivl_path_source_posedge(path);
    int pneg = ivl_path_source_negedge(path);
    const char* edge = ppos ? " +" : pneg ? " -" : "";
    ivl_signal_t src_sig;

    fprintf(vvp_out, ",\n   %s%s", src_drivers[idx], edge);
    fprintf(vvp_out,
            " (%" PRIu64 ",%" PRIu64 ",%" PRIu64 ", %" PRIu64 ",%" PRIu64
            ",%" PRIu64 ", %" PRIu64 ",%" PRIu64 ",%" PRIu64 ", %" PRIu64
            ",%" PRIu64 ",%" PRIu64,
            ivl_path_delay(path, IVL_PE_01), ivl_path_delay(path, IVL_PE_10),
            ivl_path_delay(path, IVL_PE_0z), ivl_path_delay(path, IVL_PE_z1),
            ivl_path_delay(path, IVL_PE_1z), ivl_path_delay(path, IVL_PE_z0),
            ivl_path_delay(path, IVL_PE_0x), ivl_path_delay(path, IVL_PE_x1),
            ivl_path_delay(path, IVL_PE_1x), ivl_path_delay(path, IVL_PE_x0),
            ivl_path_delay(path, IVL_PE_xz), ivl_path_delay(path, IVL_PE_zx));

    if (con_drivers[idx]) {
      fprintf(vvp_out, " ? %s", con_drivers[idx]);
    }

    fprintf(vvp_out, ")");

    src_sig = find_path_source_port(path);
    fprintf(vvp_out, " v%p_0", src_sig);
  }

  fprintf(vvp_out, ";\n");

  free(src_drivers);
  free(con_drivers);
}

struct modpath_item {
  ivl_signal_t path_sig;
  char* drive_label;
  struct modpath_item* next;
};

static struct modpath_item* modpath_list = 0;

void draw_modpath(ivl_signal_t path_sig, char* drive_label) {
  struct modpath_item* cur = calloc(1, sizeof(struct modpath_item));
  cur->path_sig = path_sig;
  cur->drive_label = drive_label;
  cur->next = modpath_list;
  modpath_list = cur;
}

void cleanup_modpath(void) {
  while (modpath_list) {
    struct modpath_item* cur = modpath_list;
    char modpath_label[64];

    modpath_list = cur->next;

    snprintf(modpath_label, sizeof modpath_label, "V_%p/m", cur->path_sig);
    draw_modpath_record(modpath_label, cur->drive_label, cur->path_sig);
    free(cur->drive_label);
    free(cur);
  }
}
